/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <gtest/gtest.h>

#include "card_channel_test_helper.h"
#include "command_apdu_test_helper.h"

namespace OHOS {
namespace SeBaseServices {
namespace UnitTest {
using namespace testing;

TEST(CardChannelTest, ConstructCardChannelWithNullptr)
{
    {
        CardChannel *channel = ConstructCardChannel(nullptr, nullptr);
        EXPECT_EQ(channel, nullptr);
    }
    {
        ChannelOperations oper {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};

        CardChannel *channel = ConstructCardChannel(nullptr, &oper);
        ASSERT_NE(channel, nullptr);

        DestructCardChannel(channel);
    }
}

TEST(CardChannelTest, DestructCardChannelWithNullptr)
{
    // will not crash
    DestructCardChannel(nullptr);
}

TEST(CardChannelTest, TransmitApduWithInvalidInput)
{
    {
        ResultCode ret = ChannelTransmitApdu(nullptr, nullptr, nullptr);
        EXPECT_EQ(ret, INVALID_PARA_NULL_PTR);
    }

    {
        ChannelOperations oper = {.transmit = &ChannelOperationsMock::MockChannelTransmit};
        CardChannelTestHelper channel(&oper);
        ResultCode ret = ChannelTransmitApdu(channel.Get(), nullptr, nullptr);
        EXPECT_EQ(ret, INVALID_PARA_NULL_PTR);
    }

    {
        ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
        const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
        CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
        ASSERT_NE(cmd.Get(), nullptr);

        ChannelOperations oper = {.transmit = &ChannelOperationsMock::MockChannelTransmit};
        CardChannelTestHelper channel(&oper);
        ResultCode ret = ChannelTransmitApdu(channel.Get(), cmd.Get(), nullptr);
        EXPECT_EQ(ret, INVALID_PARA_NULL_PTR);
    }

    {
        ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
        const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
        CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
        ASSERT_NE(cmd.Get(), nullptr);

        ChannelOperations oper = {.transmit = &ChannelOperationsMock::MockChannelTransmit};
        CardChannelTestHelper channel(&oper);
        ResultCode ret = ChannelTransmitApdu(channel.Get(), cmd.Get(), nullptr);
        EXPECT_EQ(ret, INVALID_PARA_NULL_PTR);
    }
}

TEST(CardChannelTest, TransmitApduWithInvalidInputCase1)
{
    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};

    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);

    ASSERT_NE(cmd.Get(), nullptr);

    CardChannelTestHelper channel;

    ResultCode ret = ChannelTransmitApdu(channel.Get(), cmd.Get(), nullptr);
    EXPECT_EQ(ret, INVALID_PARA_NULL_PTR);
}

TEST(CardChannelTest, TransmitApduWithAllParaCase0)
{
    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
    ASSERT_NE(cmd.Get(), nullptr);

    CardChannelTestHelper channel;
    ResponseApdu *response = nullptr;

    ResultCode ret = ChannelTransmitApdu(channel.Get(), cmd.Get(), &response);
    EXPECT_EQ(ret, INVALID_PARA_NULL_PTR);
}

TEST(CardChannelTest, TransmitApduWithAllParaCase1)
{
    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
    ASSERT_NE(cmd.Get(), nullptr);

    ChannelOperationsMock mock;
    ChannelOperations oper = {.transmit = &ChannelOperationsMock::MockChannelTransmit};
    CardChannelTestHelper channel(&oper);
    ResponseApdu *response = nullptr;

    EXPECT_CALL(mock, ChannelTransmit).Times(Exactly(1)).WillOnce(Return(RES_APDU_DATA_ERR_INDEX_4));

    ResultCode ret = ChannelTransmitApdu(channel.Get(), cmd.Get(), &response);
    EXPECT_EQ(response, nullptr);
    EXPECT_EQ(ret, RES_APDU_DATA_ERR_INDEX_4);
}

TEST(CardChannelTest, TransmitApduWithAllParaCase2)
{
    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
    ASSERT_NE(cmd.Get(), nullptr);

    ChannelOperationsMock mock;
    ChannelOperations oper = {.transmit = &ChannelOperationsMock::MockChannelTransmit};
    CardChannelTestHelper channel(&oper);
    ResponseApdu *response = nullptr;

    EXPECT_CALL(mock, ChannelTransmit).Times(Exactly(1)).WillOnce(Return(SUCCESS));

    ResultCode ret = ChannelTransmitApdu(channel.Get(), cmd.Get(), &response);
    EXPECT_NE(response, nullptr);
    EXPECT_EQ(ret, SUCCESS);

    DestroyResponseApdu(response);
}

TEST(CardChannelTest, ChannelOpenTransmitCloseWithRetryWith15Times)
{
    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
    ASSERT_NE(cmd.Get(), nullptr);

    ChannelOperationsMock mock;
    ChannelOperations oper = {
        .open = &ChannelOperationsMock::MockChannelOpen,
        .close = &ChannelOperationsMock::MockChannelClose,
        .transmit = &ChannelOperationsMock::MockChannelTransmit,
    };

    EXPECT_CALL(mock, ChannelTransmit).Times(Exactly(15)).WillRepeatedly(Return(ERR_GENERIC_ERR));
    EXPECT_CALL(mock, ChannelOpen).Times(Exactly(15)).WillRepeatedly(Return(SUCCESS));
    EXPECT_CALL(mock, ChannelClose).Times(Exactly(15)).WillRepeatedly(Return(SUCCESS));

    CardChannelTestHelper channel(&oper);

    ResponseApdu *response = nullptr;

    ResultCode ret = ChannelOpenTransmitCloseWithRetry(channel.Get(), 15, nullptr, cmd.Get(), &response);
    EXPECT_EQ(response, nullptr);
    EXPECT_EQ(ret, ERR_GENERIC_ERR);
}

TEST(CardChannelTest, ChannelOpenTransmitCloseWithRetryFirstFailedThenSuccess)
{
    uint32_t retry = 3;

    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
    ASSERT_NE(cmd.Get(), nullptr);

    ChannelOperationsMock mock;
    ChannelOperations oper = {
        .open = &ChannelOperationsMock::MockChannelOpen,
        .close = &ChannelOperationsMock::MockChannelClose,
        .transmit = &ChannelOperationsMock::MockChannelTransmit,
    };

    EXPECT_CALL(mock, ChannelTransmit).Times(Exactly(2)).WillOnce(Return(ERR_GENERIC_ERR)).WillOnce(Return(SUCCESS));
    EXPECT_CALL(mock, ChannelOpen).Times(Exactly(2)).WillRepeatedly(Return(SUCCESS));
    EXPECT_CALL(mock, ChannelClose).Times(Exactly(2)).WillRepeatedly(Return(SUCCESS));

    CardChannelTestHelper channel(&oper);

    ResponseApdu *response = nullptr;

    ResultCode ret = ChannelOpenTransmitCloseWithRetry(channel.Get(), retry, nullptr, cmd.Get(), &response);
    EXPECT_NE(response, nullptr);
    EXPECT_EQ(ret, SUCCESS);

    DestroyResponseApdu(response);
}

TEST(CardChannelTest, ChannelOpenTransmitCloseWithRetrySuccessAtFirstTime)
{
    uint32_t retry = 15;

    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
    ASSERT_NE(cmd.Get(), nullptr);

    ChannelOperationsMock mock;
    ChannelOperations oper = {
        .open = &ChannelOperationsMock::MockChannelOpen,
        .close = &ChannelOperationsMock::MockChannelClose,
        .transmit = &ChannelOperationsMock::MockChannelTransmit,
    };

    EXPECT_CALL(mock, ChannelTransmit).Times(Exactly(1)).WillRepeatedly(Return(SUCCESS));
    EXPECT_CALL(mock, ChannelOpen).Times(Exactly(1)).WillRepeatedly(Return(SUCCESS));
    EXPECT_CALL(mock, ChannelClose).Times(Exactly(1)).WillRepeatedly(Return(SUCCESS));

    CardChannelTestHelper channel(&oper);

    ResponseApdu *response = nullptr;

    ResultCode ret = ChannelOpenTransmitCloseWithRetry(channel.Get(), retry, nullptr, cmd.Get(), &response);
    EXPECT_NE(response, nullptr);
    EXPECT_EQ(ret, SUCCESS);

    DestroyResponseApdu(response);
}

TEST(CardChannelTest, ChannelOpenTransmitCloseWithRetryWillRetry15TimesWhenCheckerFailed)
{
    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
    ASSERT_NE(cmd.Get(), nullptr);

    ChannelOperationsMock mock;
    ChannelOperations oper = {
        .open = &ChannelOperationsMock::MockChannelOpen,
        .close = &ChannelOperationsMock::MockChannelClose,
        .transmit = &ChannelOperationsMock::MockChannelTransmit,
    };

    EXPECT_CALL(mock, ChannelTransmit).Times(Exactly(15)).WillRepeatedly(Return(SUCCESS));
    EXPECT_CALL(mock, ChannelOpen).Times(Exactly(15)).WillRepeatedly(Return(SUCCESS));
    EXPECT_CALL(mock, ChannelClose).Times(Exactly(15)).WillRepeatedly(Return(SUCCESS));

    CardChannelTestHelper channel(&oper);

    ResponseApdu *response = nullptr;

    auto checker = [](const ResponseApdu *) { return ERR_GENERIC_ERR; };

    ResultCode ret = ChannelOpenTransmitCloseWithRetry(channel.Get(), 15, checker, cmd.Get(), &response);
    EXPECT_EQ(response, nullptr);
    EXPECT_EQ(ret, ERR_GENERIC_ERR);
}

TEST(CardChannelTest, ChannelOpenTransmitCloseWithRetryWillRetry15TimesWhenChannelOpenFailed)
{
    ApduCommandHeader header = {0x00, 0xa4, 0x04, 0x00};
    const uint8_t data[] = {0x00, 0x01, 0x02, 0x03};
    CommandApduTestHelper cmd(&header, data, sizeof(data) / sizeof(uint8_t), MAX_APDU_RESP_SIZE);
    ASSERT_NE(cmd.Get(), nullptr);

    ChannelOperationsMock mock;
    ChannelOperations oper = {
        .open = &ChannelOperationsMock::MockChannelOpen,
        .close = &ChannelOperationsMock::MockChannelClose,
        .transmit = &ChannelOperationsMock::MockChannelTransmit,
    };

    EXPECT_CALL(mock, ChannelOpen).Times(Exactly(15)).WillRepeatedly(Return(NOT_SUPPORT));
    EXPECT_CALL(mock, ChannelTransmit).Times(Exactly(0));
    EXPECT_CALL(mock, ChannelClose).Times(Exactly(0));

    CardChannelTestHelper channel(&oper);

    ResponseApdu *response = nullptr;

    ResultCode ret = ChannelOpenTransmitCloseWithRetry(channel.Get(), 15, nullptr, cmd.Get(), &response);
    EXPECT_EQ(response, nullptr);
    EXPECT_EQ(ret, NOT_SUPPORT);
}

TEST(CardChannelTest, ChannelOpenChannelNullInput)
{
    ResultCode ret = ChannelOpenChannel(nullptr);
    EXPECT_EQ(ret, CHN_OPEN_ERR);
}

TEST(CardChannelTest, ChannelOpenChannelNotDefined)
{
    ChannelOperations oper = {
        .open = nullptr,
        .close = &ChannelOperationsMock::MockChannelClose,
        .transmit = &ChannelOperationsMock::MockChannelTransmit,
    };
    CardChannelTestHelper channel(&oper);
    ResultCode ret = ChannelOpenChannel(channel.Get());
    EXPECT_EQ(ret, SUCCESS);
}

TEST(CardChannelTest, ChannelCloseChannelNullInput)
{
    ResultCode ret = ChannelCloseChannel(nullptr);
    EXPECT_EQ(ret, CHN_CLOSE_ERR);
}

TEST(CardChannelTest, ChannelCloseChannelNotDefined)
{
    ChannelOperations oper = {
        .open = nullptr,
        .close = nullptr,
        .transmit = &ChannelOperationsMock::MockChannelTransmit,
    };
    CardChannelTestHelper channel(&oper);
    ResultCode ret = ChannelCloseChannel(channel.Get());
    EXPECT_EQ(ret, SUCCESS);
}

} // namespace UnitTest
} // namespace SeBaseServices
} // namespace OHOS